#pragma once

// Copyright (c) 2010 Manuel Peinado Gallego <manuel.peinado@gmail.com>
// Distributed under the MIT license

// View3d.h : interface of the View3d class
//

#include "GL.h"
#include "SmartEnum.h"
#include "WorldFollower.h"
#include "UiFollower.h"
#include "Ray.h"
#include <alg3d/alg3d.h>
#include <set>
class Pickable;
class SelectionManager;
class RenderListener;

typedef CPoint ScreenPoint;

struct DrawableOrderComparer
{
    bool operator()(ref<Drawable> a, ref<Drawable> b) const;
    bool operator()(Drawable* a, Drawable* b) const;
};




BEGIN_ENUM(ProjectionMode)
    Perspective, 
    Orthographic
END_ENUM(ProjectionMode)


// ----------------------------------------------------------------------------
// Class "View3d"
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
//
// ----------------------------------------------------------------------------

class View3d : public WorldFollower, public UiFollower
{
public:

    View3d(int flags=WorldFollower::IsNodeListener 
                     | WorldFollower::IsMaterialListener, 
          int uiViewFlags = UiFollower::IsSelectionListener);

    virtual ~View3d();

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // A View3D uses the method of color picking for determining what object
    // does currently occupy a given screen position. This method is simpler
    // and more robust methods such as ray-casting or OGL selection or
    // feedback (the main drawack is that it's only simple provided that
    // the display supports true color; our system works under this 
    // assumption). With the color picking method, the scene is drawn using
    // a simplistic color and lighting model everytime a pick query is 
    // performed. This function tells whether the scene is currently being
    // drawn as part of the regular rendering mechanism or as part of a pick
    // query
    bool isDrawingInPickMode() const;

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    void addDrawable(ref<Drawable> drawable);
    void removeDrawable(ref<Drawable> drawable);

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    void addPickable(ref<Pickable> pickable);
    void removePickable(ref<Pickable> pickable);

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    ScreenPoint project(const gx::Vec3 & v) const;
    gx::Vec3 unproject(const ScreenPoint & p, double z) const;

    Ray computePickRay(const ScreenPoint & p) const;

    // -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  

    // Projection mode (perspective or orthographic)
    //   * Default mode is perspective
    ProjectionMode getProjectionMode() const;
    void setProjectionMode(ProjectionMode mode);

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Pickable* getHoverItem() const;
    int getHoverItemPart() const;

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    gx::Mat4 getCameraMatrix() const;

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    void getDims(int & w, int & h) const;

    void invalidate();

    // -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  

    // Vertical field of view
    //   * Angle is in radians (must be in range 0-PI)
    //   * Only makes sense in perspective projection mode. In fact it is a
    //     runtime error to call any of these member functions when in 
    //     orthographic perspective mode
    //   * Default FOV is PI/4
    void setFov(double angle);
    double getFov() const;

    // -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  

    // In ortho projection mode we use the ortho scale property to simulate
    // a "zoom" effect, which in perspective mode is achieved by simply
    // moving the camera origin back and forth. 
    double getOrthoScale() const;

    // -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  

    // If you have an object at objPos (in world coordinates), this member
    // function returns the scale you have to apply to that object when you
    // render it so that it appears to be of constant size (i.e., doesn't 
    // get smaller or bigger as the camera zooms in or out)
    double computeConstantScaleFactor(const gx::Vec3& objPos) const;
    
    // -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  

    void addRenderListener(RenderListener*);

    // -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  

    void addContinuousRenderRequester(ContinuousRenderRequester*);

    // -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  

    void onIdle(double elapsedMicroSecs);

protected:

    // -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  
    
    // Notify listeners of these two events
    void beginRender();
    void endRender();

    // -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  

    std::set<RenderListener*> m_renderListeners;

    // -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  
    // OGL stuff
    
    //// TODO these are too specific of a GL view, they belong in a more
    //// specialized subclass
    //HGLRC m_hRC;
    //void MakeCurrent(void);


    // -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  
    // Simply width / height
    double getAspect() const;

    // -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  
    // Selection/Node/MaterialListener overrides

    void onSelectionChanged(SelectionManager* sender) override;
    
    void onNodeEvent(const NodeEvent&) override;

    void onMaterialEvent(const MaterialEvent& event_) override; 
    void onShaderEvent(const ShaderEvent&) override;

    // -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  
    // The pickable currently under the mouse cursor
    
    Pickable* m_hoverItem;
    int m_hoverItemPart;

    // -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  
    
    bool m_drawingInPickMode;

    // -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  

    double m_fov;
    ProjectionMode m_projectionMode;

    double m_orthoScale;

    // -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  
    
    // Draw the window background, either gradient or even
    void drawBackground();

    Color getBackgroundColor() const { return m_backgroundColor; }
    Color m_backgroundColor;
    bool m_backgroundGradient;

    // -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  

    void setProjectionMatrix();
    void setModelViewMatrix();
    Pickable* processPick(const ScreenPoint & point, int & subName);
    Pickable* doPickTest(const ScreenPoint & point,
                                  int & subName);
    
    virtual gx::Vec3 unprojectImpl(const ScreenPoint & p, double z) const = 0;

    virtual ScreenPoint projectImpl(const gx::Vec3 & v) const = 0;

    virtual gx::Mat4 getCameraMatrixImpl() const = 0;

    virtual void getDimsImpl(int & w, int & h) const = 0;

    virtual void invalidateImpl() = 0;

    typedef std::set<ref<Drawable>, DrawableOrderComparer> DrawableSet;
    DrawableSet m_drawables;

    // No need to store ref<Pickable> because all Pickables in this
    // set are also present in the set of Drawables, which does store
    // shared pointers. Same goes for the Pick colors map
    typedef std::set<Pickable*, 
                     DrawableOrderComparer> PickableSet;
    PickableSet m_pickables;

    PickColor findFirstUnusedPickColor() const;

    typedef std::map<PickColor, Pickable*> NameToPickableMap;
    NameToPickableMap m_pickableColors;
    bool m_drawing;

    // -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  


    // ************************************************************************
    // Camera stuff

    void cameraDolly(double amount);
    void cameraShift(double dX, double dY);

    gx::Mat4 m_cameraMat; // Current camera matrix
    gx::Mat4 m_iniCam; // Cursor matrix at the time the camera action started

    bool m_middleBtnDown; // Means that we are inside a camera action 


    void onCameraDrag(CPoint iniPoint, int currFlags, CPoint currPoint);

    // -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  

    bool isKeyDown(int key) const 
    {
        return m_keyDown.find(key) != m_keyDown.end();
    }
    void setKeyDown(int key) 
    { 
        m_keyDown.insert(key); 
    }
    void setKeyUp(int key) 
    { 
        std::set<int>::iterator it = m_keyDown.find(key);
        if(it != m_keyDown.end())
        {
            m_keyDown.erase(it); 
        }
    }
    std::set<int> m_keyDown;

    // -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  

    std::set<ContinuousRenderRequester*> m_continuousRenderRequesters;
};




